#include <GL/glew.h>
#include <GL/glut.h>
#include <GL/glu.h>
#include <stdio.h>
#include<vector>
#include<list>
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>

#include <cubeInstance.hh>
#include <cubeWorld.hh>
#include <GLBufferManager.hh>
#include <GLShaderManager.hh>

#include <cubE/cubECamera.hh>

static int _windowWidth = 0;
static int _windowHeight = 0;

static int _cameraLastPosX = 0;
static int _cameraLastPosY = 0;

static int _cameraCurrPosX = 0;
static int _cameraCurrPosY = 0;

static bool _mouseLeftButtonDown = false;
static bool _mouseRightButtonDown = false;
static bool _keySpacePressed = false;

static GLfloat vertices[] = {
   -1.0, -1.0, -1.0,   //0.left-bottom-back
   -1.0, -1.0,  1.0,   //1.left-bottom-front
   -1.0,  1.0,  1.0,   //2.left-top-front
   -1.0,  1.0, -1.0,   //3.left-top-back
    1.0, -1.0, -1.0,   //4.right-bottom-back
    1.0,  1.0, -1.0,   //5.right-top-back
    1.0,  1.0,  1.0,   //6.right-top-front
    1.0, -1.0,  1.0,   //7.right-bottom-front
   
};

static GLint indices[] = {
   0, 1, 2, 3, // left-face
   1, 7 ,6, 2, // front-face
   4, 5, 6, 7, // right-face
   4, 0, 3, 5, // back-face
   0 ,4, 7, 1, // bottom-face
   2, 6, 5, 3, // top-face
};

CubE::Camera* _camera;


#define INTERNAL

void init(void);
void reshape(int w, int h);
void keyPressed(unsigned char key, int x, int y);
void keyReleased(unsigned char key, int x, int y);
void mousePressed(int button, int state, int x, int y);
void mouseMotion(int x, int y);

void display(void);
void drawCubic(void);
GLuint SetShaders(void);


bool CheckShaderCompile(GLuint shader, std::string& error);
bool CheckShaderLink(GLuint program, std::string& error);
std::string readASCIIFile(const char* file);
GLuint CompileShader(const char* shaderName, GLenum shaderType);
GLuint LinkShaders(const std::vector<GLuint>& shaderVector);

void SetBuffer(void);

bool CubyInit(void);


void init(void)
{
   glClearColor(0.0, 0.0, 0.0, 1.0);
   glShadeModel(GL_SMOOTH);

   //check opengl version
#ifdef INTERNAL
   const char* version = (const char*)glGetString(GL_VERSION);
   printf("GL VERSION : %s\n", version);
#endif
   
   //check glew for opengl2.0
   glewInit();
   if(glewIsSupported("GL_VERSION_2_0"))
   {
      printf("yes! glew support version2.0\n");
   }
   else
   {
      printf("no! it doesn't support version2.0\n");
   }

   glEnableClientState(GL_VERTEX_ARRAY);
   glVertexPointer(3, GL_FLOAT, 0, vertices);
   glEnable(GL_DEPTH_TEST);

   //init camera
   _camera = new CubE::Camera(CubE::Camera::TARGET_CAM, 10,0,0, 0,0,0, 0.1, 5000);
   
   CubyInit();
}


void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei)w, (GLsizei)h);
   printf("width=%d, height=%d\n\n", w, h);
   
   _windowWidth = w;
   _windowHeight = h;
   
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   
   float near = _camera ? _camera->GetNearClipDistance() : 0.1f;
   float far = _camera ? _camera->GetFarClipDistance() : 5000.0f;
   gluPerspective(45, (double)w/(double)h, near, far);
   glMatrixMode(GL_MODELVIEW);
}

void mousePressed(int button, int state, int x, int y)
{
   if(GLUT_LEFT_BUTTON==button) {
      if(GLUT_DOWN==state) {
	 _mouseLeftButtonDown = true;
	 _cameraLastPosX = x;
	 _cameraLastPosY = y;
      }
      else if(GLUT_UP==state) {
	 _mouseLeftButtonDown = false;
      }
   }
   else if(GLUT_RIGHT_BUTTON==button) {
      if(GLUT_DOWN==state) {
	 _mouseRightButtonDown = true;
	 _cameraLastPosX = x;
	 _cameraLastPosY = y;
      }
      else if(GLUT_UP==state) {
	 _mouseRightButtonDown = false;
      }
   }
}

void mouseMotion(int x, int y)
{
   if(_camera) {
      
      int changeX = x - _cameraLastPosX;
      int changeY = y - _cameraLastPosY;
      
      if(_mouseLeftButtonDown && !_keySpacePressed) {
	 CubE::Degree degX(-changeX*0.1);
	 CubE::Degree degY(-changeY*0.1);
	 _camera->Rotate(CubE::Vector3::UNIT_X, degY);
         _camera->Rotate(CubE::Vector3::UNIT_Y, degX);

//      printf("degx=%f, degy=%f\n", degX.Value(), degY.Value());
      }
      else if(_mouseLeftButtonDown && _keySpacePressed){
	 _camera->Move(changeX*(-0.01), changeY*(0.01), 0);
      }
      else if(_mouseRightButtonDown) {
	 _camera->Move(0, 0, changeX*(-0.01));
      }
      _cameraLastPosX = x;
      _cameraLastPosY = y;

   }
}

void keyPressed(unsigned char key, int x, int y)
{
   //since keyboard is to control camera, we simply return void if
   //there is no camera.
   if(!_camera) return;
   
   switch(key)
   {
   case 'a':
      {
	 _camera->Move(-0.1, 0, 0);
	 break;
      }

   case 'd':
      {
	 _camera->Move(0.1, 0, 0);
	 break;
      }

   case 'w':
      _camera->Move(0, 0, -0.1);
      break;

   case 's':
      _camera->Move(0, 0, 0.1);
      break;

   case 32:      // ascii of spacebar
      _keySpacePressed = true;
      _cameraLastPosX = x;
      _cameraLastPosY = y;
      break;

   default:
      break;
   }
   float camX, camY, camZ;
   _camera->GetPosition(camX, camY, camZ);
   //   printf("camX=%f, camY=%f, camZ=%f\n", camX, camY, camZ);
}

void keyReleased(unsigned char key, int x, int y)
{
   if(!_camera) { return; }

   switch(key)
   {
   case 32:      // ascii of spacebar
      _keySpacePressed = false;
      break;
      
   default:
      break;
   }
}

void draw(GLuint* indices)
{
   glDrawElementsInstanced(GL_QUADS, 24, GL_UNSIGNED_INT, indices, 2);
}


void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

   glLoadIdentity();

   float pos[3] = {-10, 10, 10 };
   float lookat[3] = {0, 0, 0 };
   float up[3] = {0, 1, 0 };
   if(_camera) {
      _camera->GetPosition().Get(pos);
      _camera->GetLookat().Get(lookat);
      _camera->GetUpDirection().Get(up);
   }
   
   // pos[0] += (pos[0] - lookat[0]);
   // pos[1] += (pos[1] - lookat[1]);
   // pos[2] += (pos[2] - lookat[2]);
   
   gluLookAt(pos[0], pos[1], pos[2],
	     lookat[0], lookat[1], lookat[2],
	     up[0],up[1],up[2]);

   glDrawElementsInstanced(GL_QUADS,24, GL_UNSIGNED_INT, indices,8);

//   glDrawElements(GL_QUADS,24, GL_UNSIGNED_INT, indices);
   
   glutSwapBuffers();
   glutPostRedisplay();  //--Here it is!!!
}


std::string readASCIIFile(const char* file)
{
   std::ifstream ifs(file);
   if(ifs.is_open())
   {
      std::stringstream ss;
      ss<<ifs.rdbuf();
      return ss.str();
   }

   return "";
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB);
   glutInitWindowSize(800, 600);
   glutInitWindowPosition(0, 0);
   glutCreateWindow("MyCubicPixel");
   init();
   
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyPressed);
   glutKeyboardUpFunc(keyReleased);
   glutMouseFunc(mousePressed);
   glutMotionFunc(mouseMotion);
   glutMainLoop();
   return 0;

}

void SetCubeInstanceAttrib(void)
{
   CubeInstanceManager* cubeInstMgr =  CubeInstanceManager::GetSingleton();
   if(!cubeInstMgr) return;

   cubeInstMgr->GetCubeInstance(0)->SetDataColor(1.0f, 0.0f, 0.0f, 1.0f);
   cubeInstMgr->GetCubeInstance(1)->SetDataColor(0.0f, 1.0f, 0.0f, 1.0f);
//   cubeInstMgr->GetCubeInstance(1)->Disable();

   cubeInstMgr->GetCubeInstance(2)->SetDataColor(0.0f, 0.0f, 1.0f, 1.0f);
   cubeInstMgr->GetCubeInstance(3)->SetDataColor(1.0f, 1.0f, 0.0f, 1.0f);
   cubeInstMgr->GetCubeInstance(4)->SetDataColor(0.0f, 1.0f, 1.0f, 1.0f);
   cubeInstMgr->GetCubeInstance(5)->SetDataColor(1.0f, 1.0f, 1.0f, 1.0f);
//   cubeInstMgr->GetCubeInstance(5)->Disable();
//      cubeInstMgr->GetCubeInstance(6)->Disable();

}


bool CubyInit(void)
{
   GLBufferManager* oglBufMgr = GLBufferManager::GetSingleton();
   if(!oglBufMgr) return false;
   
   GLShaderManager* oglShaderMgr = GLShaderManager::GetSingleton(); 
   if(!oglShaderMgr) return false;
   
   CubeInstanceManager* cubeInstMgr =  CubeInstanceManager::GetSingleton();
   if(!cubeInstMgr) return false;

      //prepare for GL Shader
   if(!oglShaderMgr->CompileShader("myinstance.glsl", GL_VERTEX_SHADER)) {
      return false;
   }
   if(!oglShaderMgr->CompileShader("mypixel.glsl", GL_FRAGMENT_SHADER)) {
      return false;
   }
   if(!oglShaderMgr->LinkToProgram()){
      return false;
   }
   
   oglShaderMgr->Run();


   //prepare for Cube World
   CubeWorld* cubeWorld = new CubeWorld(3, 2, 2, 2);

   //prepare for Cube Instance
   if(!cubeInstMgr->PullCubeInstFrom(cubeWorld)) {
      return false;
   }

   SetCubeInstanceAttrib();

   // prepare for GL Buffer
   unsigned int
      oglBufID = oglBufMgr->CreateGLBuffer(
   	 "ogl_inst_buffer",
   	 cubeInstMgr->GetCubeInstPointer(),
   	 cubeInstMgr->GetCubeInstTotalByteSize());


//   Bind CubeInstance data with GLShader and GLBuffer
  cubeInstMgr->PushCubeInstTo(oglBufID);

   return true;
}
